import * as parser from '@babel/parser';
import * as t from '@babel/types';
import traverse from '@babel/traverse';
import generate from '@babel/generator';
import SchemaMeta from './schema-meta';
import { LifeCycleIdentifiers } from '../types';
import type { ParserProps, ParserContructorProps, SourceCode } from '../types';

const lifeCycleIdentifiersMap = Object.values(LifeCycleIdentifiers).reduce<Record<string, string>>(
  (acc, cur) => ({
    ...acc,
    [cur]: cur,
  }),
  {},
);
export default class Parser extends SchemaMeta implements ParserProps {
  private codes?: string[];

  constructor(props: ParserContructorProps) {
    super(props);
  }

  protected async parseMethods(): Promise<SourceCode['methods']> {
    const codes = Object.values(this.schema.methods || {}).reduce<string[]>((acc, cur) => {
      if (cur.value) acc.push(cur.value);
      return acc;
    }, []);
    if (codes.length) {
      this.codes = codes.reduce<string[]>((acc, method) => {
        const ast = parser.parse(method);
        traverse(ast, {
          Identifier(path) {
            const typeStr = path.parentPath.toString();
            switch (path.node.name) {
              case 'setState':
                if (typeStr === 'this.setState') {
                  path.parentPath.replaceWith(t.identifier('setState'));
                }
                break;
              case 'state':
                if (typeStr === 'this.state') {
                  path.parentPath.replaceWith(t.identifier('state'));
                }
            }
          },
        });
        acc.push(generate(ast).code);
        return acc;
      }, []);
    }
    return this.codes;
  }

  private createUseEffect(lifeCycle: string): string {
    return `useEffect(() => {
      ${lifeCycle}
      return () => {}
    }, [])`;
  }

  protected async parseLifeCycles(): Promise<SourceCode['lifeCycles']> {
    const codes = Object.values(this.schema.lifeCycles || {}).reduce<string[]>((acc, cur) => {
      if (cur.value) acc.push(cur.value);
      return acc;
    }, []);

    if (codes.length) {
      this.codes = codes.reduce<string[]>((acc, method) => {
        const ast = parser.parse(this.createUseEffect(method));
        traverse(ast, {
          // 标识符（变量名、属性名、函数名、参数）
          Identifier(path) {
            switch (path.node.name) {
              case 'setState':
                if (path.parentPath.toString() === 'this.setState') {
                  path.parentPath.replaceWith(t.identifier('setState'));
                }
                break;
              case 'state':
                if (path.parentPath.toString() === 'this.state') {
                  path.parentPath.replaceWith(t.identifier('state'));
                }
              default:
            }
          },
          // 块级语句
          BlockStatement(path) {
            if (path.parentPath.type === 'FunctionDeclaration' && t.isIdentifier((path.parentPath.node as any).id)) {
              const lifeCycleIdentifier = lifeCycleIdentifiersMap[(path.parentPath.node as any).id.name];
              if (lifeCycleIdentifier) {
                if ((path.parentPath.node as any).async) {
                  /**
                   * 处理生命周期，为了避免声明提前以及在useEffect中执行异步函数，将函数式声明转为箭头函数，例如：
                   * function componentDidMount() {} 转为 const componentDidMount = async () => {}; componentDidMount();
                   */
                  path.parentPath.replaceWith(
                    t.variableDeclaration('const', [
                      {
                        type: 'VariableDeclarator',
                        id: t.identifier(lifeCycleIdentifier),
                        init: t.arrowFunctionExpression([], path.node, true),
                      },
                    ]),
                  );
                  path.parentPath.insertAfter(
                    t.expressionStatement(t.callExpression(t.identifier(lifeCycleIdentifier), [])),
                  );
                } else {
                  path.parentPath.replaceWithMultiple(path.node.body);
                }
              }
            }
          },
        });
        acc.push(generate(ast).code);
        return acc;
      }, []);
    }
    return this.codes;
  }
}
